package com.gitee.fastmybatis.core.util;

import com.gitee.fastmybatis.annotation.Column;
import com.gitee.fastmybatis.core.ext.code.util.FieldUtil;
import com.gitee.fastmybatis.core.support.ColumnMeta;
import com.gitee.fastmybatis.core.support.Getter;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author tanghc
 */
public class ClassUtil {

    public static final String PROXY_PREFIX = "com.sun.proxy";

    public static final String GET = "get";

    public static final String IS = "is";

    private static final Map<String, Class<?>> CACHE = new ConcurrentHashMap<>(16);

    private ClassUtil() {
    }


    public static Class<?> getSuperClassGenericTypeCache(Class<?> clazz, int index) {
        return CACHE.computeIfAbsent(clazz.getName() + index, k -> getSuperClassGenericType(clazz, index));
    }

    /**
     * 返回定义类时的泛型参数的类型. <br>
     * 如:定义一个BookManager类<br>
     * <code>{@literal public BookManager extends GenricManager<Book,Address>}{...} </code>
     * <br>
     * 调用getSuperClassGenricType(getClass(),0)将返回Book的Class类型<br>
     * 调用getSuperClassGenricType(getClass(),1)将返回Address的Class类型
     *
     * @param clazz 从哪个类中获取
     * @param index 泛型参数索引,从0开始
     * @return 返回泛型类型class
     * @throws IndexOutOfBoundsException
     */
    public static Class<?> getSuperClassGenericType(Class<?> clazz, int index) throws IndexOutOfBoundsException {

        Type genType = clazz.getGenericSuperclass();

        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }

        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

        if (index >= params.length || index < 0) {
            return Object.class;
        }
        if (!(params[index] instanceof Class)) {
            return Object.class;
        }
        return (Class<?>) params[index];
    }

    /**
     * 返回接口类的泛型参数的类型
     *
     * @param clazz 从哪个类中获取
     * @param index 泛型参数索引,从0开始
     * @return 返回class对象
     */
    public static Class<?> getSuperInterfaceGenericTypeCache(Class<?> clazz, int index) {
        return CACHE.computeIfAbsent(clazz.getName() + index, k -> getSuperInterfaceGenericType(clazz, index));
    }

    public static Class<?> getRealMapperClass(Class<?> mapperClass) {
        String name = mapperClass.getName();
        if (name.startsWith(PROXY_PREFIX)) {
            Type[] interfacesTypes = mapperClass.getGenericInterfaces();
            if (interfacesTypes.length == 0) {
                return Object.class;
            }
            return (Class<?>) interfacesTypes[0];
        }
        return mapperClass;
    }

    public static Class<?> getSuperInterfaceGenericType(Class<?> clazz, int index) {
        clazz = getRealMapperClass(clazz);
        // 如果是mapper代理类
        // 一个类可能实现多个接口,每个接口上定义的泛型类型都可取到
        Type[] interfacesTypes = clazz.getGenericInterfaces();
        if (interfacesTypes.length == 0) {
            return Object.class;
        }
        // 取第一个接口
        Type firstInterface = interfacesTypes[0];
        Type[] params = ((ParameterizedType) firstInterface).getActualTypeArguments();

        if (index >= params.length || index < 0) {
            return Object.class;
        }
        if (!(params[index] instanceof Class)) {
            return Object.class;
        }

        return (Class<?>) params[index];
    }


    /**
     * 是否是数组或结合类
     *
     * @param value 待检测对象
     * @return true，是结合或数组
     */
    public static boolean isArrayOrCollection(Object value) {
        boolean ret = false;
        if (value.getClass().isArray()) {
            ret = true;
        } else if (value instanceof Collection) {
            ret = true;
        }
        return ret;
    }

    /**
     * Check if it is the basic data type of json data
     *
     * @param type0 java class name
     * @return boolean
     */
    public static boolean isPrimitive(String type0) {
        if (Objects.isNull(type0)) {
            return true;
        }
        String type = type0.contains("java.lang") ? type0.substring(type0.lastIndexOf(".") + 1, type0.length()) : type0;
        type = type.toLowerCase();
        switch (type) {
            case "string":
            case "integer":
            case "int":
            case "object":
            case "void":
            case "long":
            case "double":
            case "float":
            case "short":
            case "bigdecimal":
            case "char":
            case "character":
            case "number":
            case "boolean":
            case "byte":
            case "uuid":
            case "biginteger":
            case "java.sql.timestamp":
            case "java.util.date":
            case "java.time.localdatetime":
            case "java.time.localtime":
            case "localtime":
            case "date":
            case "localdatetime":
            case "localdate":
            case "zoneddatetime":
            case "java.time.localdate":
            case "java.time.zoneddatetime":
            case "java.math.bigdecimal":
            case "java.math.biginteger":
            case "java.util.uuid":
            case "java.io.serializable":
                return true;
            default:
                return false;
        }
    }

    public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
        Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
        for (Annotation declaredAnnotation : declaredAnnotations) {
            if (declaredAnnotation.annotationType() == annotationType) {
                return (A) declaredAnnotation;
            }
        }
        return null;
    }

    public static Field findField(Class<?> clazz, String name) {
        if (name == null) {
            return null;
        }
        Class<?> searchType = clazz;
        while (Object.class != searchType && searchType != null) {
            Field[] fields = searchType.getDeclaredFields();
            for (Field field : fields) {
                if (name.equals(field.getName())) {
                    return field;
                }
            }
            searchType = searchType.getSuperclass();
        }
        return null;
    }

    public static Method findMethod(Class<?> clazz, String methodName, Class[] params) {
        if (methodName == null) {
            return null;
        }

        try {
            return clazz.getDeclaredMethod(methodName, params);
        } catch (NoSuchMethodException e) {

            return null;
        }
    }

    public static void makeAccessible(Field field) {
        if ((!Modifier.isPublic(field.getModifiers()) ||
                !Modifier.isPublic(field.getDeclaringClass().getModifiers()) ||
                Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
            field.setAccessible(true);
        }
    }

    public static <T> T newInstance(Class<T> clazz) {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException("create instance error", e);
        }
    }

    public static Class<?> forName(String impClass) {
        String className = impClass.replace("/", ".");
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * User::getName -> name
     *
     * @param fn 方法getter函数
     * @param <T> 类型
     * @return 返回字段名
     */
    public static <T> String getColumnName(Getter<T> fn) {
        SerializedLambda lambda = getSerializedLambda(fn);
        String methodName = lambda.getImplMethodName();
        if (methodName.startsWith(GET)) {
            methodName = methodName.substring(3);
        } else if (methodName.startsWith(IS)) {
            methodName = methodName.substring(2);
        }
        String fieldName = FieldUtil.lowerFirstLetter(methodName);
        String implClass = lambda.getImplClass();
        Class<?> clazz = forName(implClass);
        Field field = findField(clazz, fieldName);
        return getColumnName(field, fieldName);
    }

    private static String getColumnName(Field field, String fieldName) {
        return Optional.ofNullable(field)
                .map(f -> f.getAnnotation(Column.class))
                .map(Column::name)
                .filter(name -> !"".equals(name))
                .orElseGet(() -> FieldUtil.camelToUnderline(fieldName));
    }

    /**
     * 获取字段class<br>
     *
     * {@literal User::getName -> Class<String> }
     *
     * @param fn 方法getter函数
     * @param <T> 类型
     * @return 返回字段名
     */
    public static <T> ColumnMeta getColumnMeta(Getter<T> fn) {
        SerializedLambda lambda = getSerializedLambda(fn);
        String methodName = lambda.getImplMethodName();
        if (methodName.startsWith(GET)) {
            methodName = methodName.substring(3);
        } else if (methodName.startsWith(IS)) {
            methodName = methodName.substring(2);
        }
        String fieldName = FieldUtil.lowerFirstLetter(methodName);
        String implClass = lambda.getImplClass();
        Class<?> clazz = forName(implClass);
        Field field = findField(clazz, fieldName);

        String columnName = getColumnName(field, fieldName);
        Class<?> columnClass = field == null ? String.class : field.getType();
        return new ColumnMeta(columnName, columnClass);
    }

    public static SerializedLambda getSerializedLambda(Serializable fn) {
        Class<? extends Serializable> clazz = fn.getClass();
        try {
            Method method = clazz.getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            return (SerializedLambda) method.invoke(fn);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }


}
